/*******************************************************************************
 * Copyright (c) 2006 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/

package com.architexa.org.eclipse.gef;

import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;

import org.eclipse.swt.widgets.Control;

import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.StructuredSelection;

/**
 * Manages a viewer's selection model. Selection management includes representing a form
 * of selection which is available to clients of a viewer as an ISelection. It also
 * includes managing the notion of focus, which is closely tied to the current selection.
 * The selection manager provides the mechanism for modifying the selection and any
 * validation.
 * <P>
 * WARNING: Subclassing this class is considered experimental at this point.
 * @since 3.2
 */
public class SelectionManager {

private EditPart focusPart;
private Runnable notifier;
private List selection;
private EditPartViewer viewer;

/**
 * Default Constructor
 * @since 3.2
 */
protected SelectionManager() { }

/**
 * Creates the default implementation for a selection manager.
 * @return the default selection manager
 * @since 3.2
 */
public static SelectionManager createDefault() {
	return new SelectionManager();
}

/**
 * Appends the <code>EditPart</code> to the current selection. The EditPart becomes the
 * new primary selection. Fires selection changed to all{@link
 * org.eclipse.jface.viewers.ISelectionChangedListener}s.
 * @param editpart the EditPart to append
 * @since 3.2
 */
public void appendSelection(EditPart editpart) {
	if (editpart != getFocus())
		viewer.setFocus(null);
	if (!selection.isEmpty()) {
		EditPart primary = (EditPart)selection.get(selection.size() - 1);
		primary.setSelected(EditPart.SELECTED);
	}
	// if the editpart is already in the list, re-order it to be the last one
	selection.remove(editpart);
	selection.add(editpart);
	editpart.setSelected(EditPart.SELECTED_PRIMARY);
	
	fireSelectionChanged();
}

/**
 * Removes the <code>EditPart</code> from the current selection.
 * @param editpart the editpart
 * @since 3.2
 */
public void deselect(EditPart editpart) {
	editpart.setSelected(EditPart.SELECTED_NONE);

	selection.remove(editpart);
	if (!selection.isEmpty()) {
		EditPart primary = (EditPart)selection.get(selection.size() - 1);
		primary.setSelected(EditPart.SELECTED_PRIMARY);
	}
	fireSelectionChanged();
}

/**
 * Deselects everything.
 * @since 3.2
 */
public void deselectAll() {
	EditPart part;
	setFocus(null);
	for (int i = 0; i < selection.size(); i++) {
		part = (EditPart)selection.get(i);
		part.setSelected(EditPart.SELECTED_NONE);
	}
	selection.clear();
	fireSelectionChanged();
}

/**
 * Causes the viewer to fire selection changed notification to all listeners.
 * @since 3.2
 */
protected final void fireSelectionChanged() {
	notifier.run();
}

/**
 * Returns the focus editpart.
 * @return the focus editpart
 * @since 3.2
 */
protected EditPart getFocus() {
	return focusPart;
}

/**
 * Returns the current selection.
 * @return the selection
 * @since 3.2
 */
public ISelection getSelection() {
	if (selection.isEmpty() && viewer.getContents() != null)
		return new StructuredSelection(viewer.getContents());
	return new StructuredSelection(selection);
}

/**
 * Returns <code>null</code> or the viewer whose selection is managed.
 * @return <code>null</code> or the viewer
 * @since 3.2
 */
protected EditPartViewer getViewer() {
	return viewer;
}

/**
 * For internal use only. This API is subject to change.
 * @param control the control
 * @since 3.2
 */
public void internalHookControl(Control control) { }

/**
 * For internal use only. This API is subject to change.
 * @since 3.2
 */
public void internalUninstall() { }

/**
 * Provides a hook for when the viewer has been set.
 * @param viewer the viewer.
 * @since 3.2
 */
protected void hookViewer(EditPartViewer viewer) { }

/**
 * For internal use only.
 * @param viewer viewer
 * @param selection selection
 * @param notifier notifier
 * @since 3.2
 */
public void internalInitialize(EditPartViewer viewer, List selection, Runnable notifier) {
	this.viewer = viewer;
	this.selection = selection;
	this.notifier = notifier;

	hookViewer(viewer);
}

/**
 * Sets the focus part.
 * @param part the focus part
 * @since 3.2
 */
public void setFocus(EditPart part) {
	if (focusPart == part)
		return;
	if (focusPart != null)
		focusPart.setFocus(false);
	focusPart = part;
	if (focusPart != null)
		focusPart.setFocus(true);
}

/**
 * Sets the selection.
 * @param newSelection the new selection
 * @since 3.2
 */
public void setSelection(ISelection newSelection) {
	if (!(newSelection instanceof IStructuredSelection))
		return;
	
	List orderedSelection = ((IStructuredSelection)newSelection).toList();
	// Convert to HashSet to optimize performance.
	Collection hashset = new HashSet(orderedSelection);

	setFocus(null);
	for (int i = 0; i < selection.size(); i++) {
		EditPart part = (EditPart)selection.get(i);
		if (!hashset.contains(part))
			part.setSelected(EditPart.SELECTED_NONE);
	}
	selection.clear();
	
	if (!orderedSelection.isEmpty()) {
		Iterator itr = orderedSelection.iterator();
		while (true) {
			EditPart part = (EditPart)itr.next();
			selection.add(part);
			if (!itr.hasNext()) {
				part.setSelected(EditPart.SELECTED_PRIMARY);
				break;
			}
			part.setSelected(EditPart.SELECTED);
		}
	}
	fireSelectionChanged();
}

}
